package com.bearingnavi.android.services;

import java.util.List;

import org.apache.log4j.Logger;

import android.app.AlertDialog;
import android.content.Context;
import android.content.Intent;
import android.hardware.GeomagneticField;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationProvider;
import android.os.Bundle;
import android.provider.Settings;

import com.bearingnavi.android.dialogs.Dialog;
import com.bearingnavi.android.overlays.CurrentPositionPointOverlay;
import com.bearingnavi.android.overlays.MeasurementOverlay;
import com.bearingnavi.android.overlays.WalkingPathOverlay;
import com.bearingnavi.android.views.RotateView;
import com.google.android.maps.GeoPoint;
import com.google.android.maps.MapView;
import com.google.android.maps.Overlay;

public class LocationService implements LocationListener {
	
	private List<Overlay> mapOverlays;
	private CurrentPositionPointOverlay currentPositionPointOverlay = new CurrentPositionPointOverlay();
	private static MeasurementOverlay measurementOverlay = new MeasurementOverlay();
	private MapView mapView;
	private static boolean followTheLocation = true;
	private static boolean holdMap = false;
	private GeoPoint lastGeoPoint = null;
	private Context context;
	private static Float declination;
//	private TextView title;
	private int numOfFix = 0;
	private Logger log = Logger.getLogger(getClass());
	private SensorService sensorService;
	private WalkingPathOverlay walkingPathOverlay = new WalkingPathOverlay();
	private static long lastRotateTime = 0; 
	private final int TIMEOUT = 60000; // milliseconds
	private static AlertDialog noGPSDialog;
	
	
	private boolean isTimeout(){
		return System.currentTimeMillis() - lastRotateTime < TIMEOUT;
	}
	
	public static void setRotateTimeout(){
		lastRotateTime = System.currentTimeMillis();
	}
	
	public static float getDeclination() {
		return declination;
	}
	
	public static float getDeclination(GeoPoint geoPoint) {
		if (geoPoint != null && declination == null) {
			declination = new GeomagneticField(
					(float) geoPoint.getLatitudeE6() / 1000000f,
					(float) geoPoint.getLongitudeE6() / 1000000f,
					0f,
					System.currentTimeMillis()).getDeclination();
		}
		return declination;
	}
	
	public static void setDestGeoPoint(GeoPoint destGeoPoint) {
		measurementOverlay.setDestGeoPoint(destGeoPoint);
	}
	
	public static void setFollowTheLocation(boolean followTheLocation) {
		LocationService.followTheLocation = followTheLocation;
	}
	
	public static boolean getFollowTheLocation() {
		return followTheLocation;
	}
	
	public static void setHoldMap(boolean holdMap) {
		LocationService.holdMap = holdMap;
	}
	
	public GeoPoint getLastGeoPoint() {
		return lastGeoPoint;
	}
	
	public LocationService(Context context, MapView mapView) {
		
		this.context = context;
		this.mapView = mapView;
		this.mapOverlays = mapView.getOverlays();
		this.sensorService = new SensorService(this, mapView, context.getResources());
		mapOverlays.add(walkingPathOverlay);
        mapOverlays.add(currentPositionPointOverlay);
        mapOverlays.add(measurementOverlay);
	}

	public void onLocationChanged(Location location) {

		GeoPoint geoPoint = null;
		
		if (location != null) {
			
			if (declination == null) {
				
				declination = new GeomagneticField(
					(float) location.getLatitude(),
					(float) location.getLongitude(),
					(float) location.getAltitude(),
					System.currentTimeMillis()).getDeclination();
			}
			
			geoPoint = new GeoPoint(
					(int) (location.getLatitude() * 1000000),
					(int) (location.getLongitude() * 1000000));
			
			numOfFix++;
			
			float currentMapBearing = RotateView.getInstance().getBearing();
			float currentLocationBearing = location.getBearing();
			
			if (!isTimeout() && !holdMap &&
				((numOfFix % 20) == 0 || Math.abs(currentMapBearing - currentLocationBearing) > 25))  {
				
				RotateView.getInstance().setBearing(currentLocationBearing);
			}
			
			log.debug("location received > Accuracy: "+location.getAccuracy()+
					", Lon: "+location.getLongitude()+", Lat: "+location.getLatitude()+
					", numOfFix: "+numOfFix);
		} else {
			
			geoPoint = lastGeoPoint;
		}
		
		if (((followTheLocation && location != null) || numOfFix == 1)
				 && !holdMap) {
		
			mapView.getController().animateTo(geoPoint);
		}
    	
    	measurementOverlay.setGeoPoint(geoPoint);
    	
    	if (geoPoint != lastGeoPoint) {
    		
//    		mapOverlays.add(new WalkingPathOverlay(lastGeoPoint, geoPoint));
    		walkingPathOverlay.addGeoPoint(geoPoint);
		}
    	
        sensorService.onCurrentPositionChanged(geoPoint);
        mapView.invalidate();
        
        lastGeoPoint = geoPoint;
	}

	public void onProviderDisabled(String provider) {

		noGPSDialog = Dialog.buildCustomDialog2(context, "Warning !", "GPS satellites is turned off.", "GPS menu", "Back", new Dialog.ConfirmHandler() {
			
			public void onConfirm() {
				
				Intent intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
				
				context.startActivity(intent);
			}
		});
		
	}

	public void onProviderEnabled(String provider) {
		
		log.debug("GPS Enabled");
		noGPSDialog.dismiss();
	}

	public void onStatusChanged(String provider, int status, Bundle extras) {
		/* This is called when the GPS status alters */
		switch (status) {
		case LocationProvider.OUT_OF_SERVICE:
			log.debug("Status Changed: Out of Service");
			walkingPathOverlay.cutWalkingPath();
			break;
		case LocationProvider.TEMPORARILY_UNAVAILABLE:
			log.debug("Status Changed: Temporarily Unavailable");
			walkingPathOverlay.cutWalkingPath();
			break;
		case LocationProvider.AVAILABLE:
			log.debug("Status Changed: Available");
			break;
		}
	}
	
	public SensorService getSensorService() {
		return sensorService;
	}

}
